package top.hcode.hoj.dao;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.DynaBean;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.nutz.lang.Lang;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import top.hcode.hoj.dao.problem.TagRelDao;
import top.hcode.hoj.manager.org.OrgUserManager;
import top.hcode.hoj.mapper.*;
import top.hcode.hoj.pojo.entity.course.*;
import top.hcode.hoj.pojo.entity.group.Group;
import top.hcode.hoj.pojo.entity.org.OrgUser;
import top.hcode.hoj.pojo.entity.problem.Problem;
import top.hcode.hoj.pojo.entity.problem.Tag;
import top.hcode.hoj.pojo.entity.user.UserInfo;

import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import static cn.hutool.core.bean.BeanUtil.getFieldValue;

/**
 * @author orangej
 * @since 2024/12/22
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class LinkLoader implements ApplicationContextAware {
    private final CourseMapper courseMapper;
    private final CourseSectionMapper sectionMapper;
    private final LessonMapper lessonMapper;
    private final QuestionMapper questionMapper;
    private final ProblemMapper problemMapper;
    private final GroupMapper groupMapper;
    private final LessonHandoutMapper handoutMapper;
    private final UserInfoMapper userMapper;
    private final TagRelDao tagRelDao;
    private final OrgUserMapper orgUserManager;

    private ApplicationContext applicationContext;

    /**
     * 根据 courseId 加载 course
     *
     * @param obj {courseId: Long, course: Course}
     */
    public void loadCourse(Object obj) {
        if (obj == null) return;

        DynaBean bean = new DynaBean(obj);
        Object courseId = bean.get("courseId");
        if (courseId == null) return;

        // query course
        Course course = courseMapper.selectById((Long) courseId);

        // set course
        bean.set("course", course);
    }

    public void loadCourse(List<?> list) {
        if (list == null || list.isEmpty()) return;

        List<DynaBean> beanList = list.stream().map(DynaBean::new).collect(Collectors.toList());

        // query course
        List<Long> courseIdList = beanList.stream().map(o -> (Long) o.get("courseId")).collect(Collectors.toList());
        Map<Long, Course> courseMap = courseMapper.selectBatchIds(courseIdList).stream()
                .collect(Collectors.toMap(Course::getId, course -> course));

        // set course
        for (DynaBean bean : beanList) {
            bean.set("course", courseMap.get((Long) bean.get("courseId")));
        }
    }

    /**
     * 根据 sectionId 加载 section
     *
     * @param obj {sectionId: Long, section: CourseSection}
     */
    public void loadCourseSection(Object obj) {
        if (obj == null) return;

        DynaBean bean = new DynaBean(obj);
        Object sectionId = bean.get("sectionId");
        if (sectionId == null) return;

        // query course section
        CourseSection section = sectionMapper.selectById((Long) sectionId);

        // set course section
        bean.set("section", section);
    }

    public void loadCourseSection(List<?> list) {
        if (list == null || list.isEmpty()) return;

        List<DynaBean> beanList = list.stream().map(DynaBean::new).collect(Collectors.toList());

        // query course
        List<Long> sectionIdList = beanList.stream().map(o -> (Long) o.get("sectionId")).collect(Collectors.toList());
        Map<Long, CourseSection> sectionMap = sectionMapper.selectBatchIds(sectionIdList).stream()
                .collect(Collectors.toMap(CourseSection::getId, section -> section));

        // set course
        for (DynaBean bean : beanList) {
            bean.set("section", sectionMap.get((Long) bean.get("sectionId")));
        }
    }

    public void loadLesson(Object obj) {
        if (obj == null) return;

        DynaBean bean = new DynaBean(obj);
        Object lessonId = bean.get("lessonId");
        if (lessonId == null) return;

        // query lesson
        Lesson lesson = lessonMapper.selectById((Long) lessonId);

        // set lesson
        bean.set("lesson", lesson);
    }

    public void loadLesson(List<?> list) {
        if (list == null || list.isEmpty()) return;

        List<DynaBean> beanList = list.stream().map(DynaBean::new).collect(Collectors.toList());

        // query lesson
        List<Long> lessonIdList = beanList.stream().map(o -> (Long) o.get("lessonId")).collect(Collectors.toList());
        Map<Long, Lesson> lessonMap = lessonMapper.selectBatchIds(lessonIdList).stream()
                .collect(Collectors.toMap(Lesson::getId, lesson -> lesson));

        // set lesson
        for (DynaBean bean : beanList) {
            bean.set("lesson", lessonMap.get((Long) bean.get("lessonId")));
        }
    }

    public void loadLinks(Object obj, String... fieldNames) {
        if (obj == null || fieldNames == null || fieldNames.length == 0) return;

        DynaBean bean = new DynaBean(obj);

        for (String fieldName : fieldNames) {
            String idFieldName = fieldName + "Id";
            if (!bean.containsProp(fieldName) || !bean.containsProp(idFieldName)) {
                continue;   // no id field
            }

            PropertyDescriptor propDescr = BeanUtil.getPropertyDescriptor(obj.getClass(), "id");
            Class<?> propType = propDescr.getPropertyType();
            BaseMapper<?> mapper = getMapper(propType);
            if (mapper == null) continue;   // no mapper


            Serializable linkId = bean.get(idFieldName);
            if (linkId == null) continue;   // id is null

            Object value = mapper.selectById(linkId);
            bean.set(fieldName, value);
        }
    }

    public void loadLinks(List<?> list, String... fieldNames) {
        if (Lang.isEmpty(list) || fieldNames == null || fieldNames.length == 0) return;

        List<DynaBean> beanList = list.stream().map(DynaBean::new).collect(Collectors.toList());

        for (String fieldName : fieldNames) {
            PropertyDescriptor propDescr = BeanUtil.getPropertyDescriptor(beanList.get(0).getBeanClass(), fieldName);
            Class<?> propType = propDescr.getPropertyType();    // 关联属性类型
            BaseMapper<?> mapper = getMapper(propType);
            if (mapper == null) continue;   // no mapper


            String idFieldName;
            if (propType == UserInfo.class) {
                idFieldName = "uid";
            } else if (propType == Group.class) {
                idFieldName = "gid";
            } else {
                idFieldName = fieldName + "Id";
            }
            if (!beanList.get(0).containsProp(fieldName) || !beanList.get(0).containsProp(idFieldName)) {
                continue;   // no id field
            }

            // 获取所有的id值
            List<Serializable> linkIdList = beanList.stream().map(o -> (Long) o.get(idFieldName))
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());
            if (Lang.isEmpty(linkIdList)) continue;

            // 查询得到 id:dbo 的 Map
            Map<Serializable, Object> linkMap = mapper.selectBatchIds(linkIdList).stream()
                    .collect(Collectors.toMap(o -> (Serializable) getFieldValue(o, "id"), o -> o));

            for (DynaBean bean : beanList) {
                Object value = linkMap.get((Serializable) bean.get(idFieldName));
                bean.set(fieldName, value);
            }
        }
    }

    BaseMapper getMapper(Class<?> entityClass) {
        String mapperName = StrUtil.lowerFirst(entityClass.getSimpleName() + "Mapper");
        BaseMapper mapper = (BaseMapper) applicationContext.getBean(mapperName);
        if (mapper == null) {
            throw new IllegalArgumentException("mapper " + mapperName + " not found");
        }

        return mapper;
    }

    /**
     * 从 tag_rel 表查询与 obj 关联的标签列表
     *
     * @param obj 支持 Course、CourseSection、Lesson、Question
     */
    public void loadTags(Object obj) {
        if (obj == null) return;

        String targetType = getTagTargetType(obj);
        if (targetType == null) return;

        loadTags(obj, targetType);
    }

    /**
     * 从 tag_rel 表查询与 obj 关联的标签列表
     *
     * @param obj        支持 Course、CourseSection、Lesson、Question
     * @param targetType question、course、section、lesson
     */
    public void loadTags(Object obj, String targetType) {
        if (obj == null) return;

        DynaBean bean = new DynaBean(obj);
        Object targetId = bean.get("id");
        if (targetId == null) return;

        List<Tag> tags = tagRelDao.getTagsByTarget((Long) targetId, targetType);
        bean.set("tags", tags);
    }

    public void loadTags(List<?> list) {
        if (Lang.isEmpty(list)) return;

        list.parallelStream().forEach(this::loadTags);
    }

    public void loadTags(List<?> list, String targetType) {
        if (Lang.isEmpty(list)) return;

        list.parallelStream().forEach(o -> loadTags(o, targetType));
    }

    public void loadLessonHandouts(Lesson lesson) {
        if (lesson == null) return;

        List<LessonHandout> handouts = handoutMapper.selectByLessonId(lesson.getId());
        lesson.setHandoutList(handouts);
    }

    public String getTagTargetType(Object obj) {
        if (obj == null) return null;

        Class<?> clazz = obj.getClass();
        if (obj instanceof Question) {
            return "question";
        } else if (obj instanceof Course) {
            return "course";
        } else if (obj instanceof Lesson) {
            return "lesson";
        } else if (obj instanceof Problem) {
            return "problem";
        } else if (obj instanceof Stuff) {
            return "stuff";
        }

        throw new RuntimeException("Unsupported target type: " + clazz.getName());
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

